/*
   This file will contain all the helper functions.
   That is, functions that include error checks for
   things such as malloc
   */

#include "cli_net.h"
#include "cli_helper.h"


//reads a line from a file or stream
char *readLine(int *fd)
{
	if(!checkForReading(*fd, 500000))
		return NULL;


	//will hold the bytes read from the socket
	//data size is the size of the final data
	ssize_t bytesRead, dataSize = BUF_LEN;

	//will be the buffer for reading in the data
	char buf[BUF_LEN];

	//zero out the buffer
	bzero(buf, BUF_LEN);


	//data ptr keep track of place in the data block,
	//so incoming data can be concatinated on to it
	char *data = NULL, *dataPtr = NULL;

	dataPtr = data = chk_realloc(data, BUF_LEN);


	//used to calculate dataPtr
	//represents the amount of iterations of the loop
	int i = 0;

	//zero out the data
	bzero(data, BUF_LEN);


	//loop until end of line
	while (!strstr(data, "\r\n\0")) {


		//read reply from server
		bytesRead = read(*fd, buf, BUF_LEN);
		//if read failed, print error and exit
		if(bytesRead < 0)
		{
			perror("Error reading from server");
			exit(1);
		}



		//copy the data into the final pointer
		memcpy(dataPtr, buf, BUF_LEN);

		//zero out the buffer
		bzero(buf, BUF_LEN);

		//increase the data size
		dataSize += BUF_LEN;

		//realloc the data
		data = chk_realloc(data, dataSize);

		i++;
		dataPtr = data + BUF_LEN * i;
		
	}

	return data;

}

/*
   reads fully from a file or stream.
   This function assumes the server socket
   will close after the data is read
   */
void *readFully(int fd)
{


	if(!checkForReading(fd, 500000))
		return NULL;



	//will hold the bytes read from the socket
	//data size is the size of the final data
	ssize_t bytesRead, dataSize = BUF_LEN;

	//will be the buffer for reading in the data
	char buf[BUF_LEN];

	//zero out the buffer
	bzero(buf, BUF_LEN);


	//data ptr keep track of place in the data block,
	//so incoming data can be concatinated on to it
	char *data = NULL, *dataPtr = NULL;

	dataPtr = data = chk_realloc(data, BUF_LEN);


	//used to calculate dataPtr
	//represents the amount of iterations of the loop
	int i = 0;

	//zero out the data
	bzero(data, BUF_LEN);


	//loop until server disconnects
	while ((bytesRead = read(fd, buf, BUF_LEN))) {


		
		//if read failed, print error and exit
		if(bytesRead < 0)
		{
			perror("Error reading from server");
			exit(1);
		}



		//copy the data into the final pointer
		memcpy(dataPtr, buf, BUF_LEN);

		//zero out the buffer
		bzero(buf, BUF_LEN);

		//increase the data size
		dataSize += BUF_LEN;

		//realloc the data
		data = chk_realloc(data, dataSize);

		i++;
		dataPtr = data + BUF_LEN * i;
		
	}

	return data;

}


bool checkForReading(int fd, int utimeout)
{
	fd_set fds;
	//will say if the socket is ready for reading
	int isReady;


	FD_ZERO(&fds);
	FD_SET(fd, &fds);

	//set a time out for the server
	struct timeval t;

	//set time out for 30 seconds
	t.tv_sec = 0;
	t.tv_usec = utimeout;

	//test to see if the socket is ready for reading
	if((isReady = select(FD_SETSIZE, &fds, NULL, NULL, &t)) < 0)
	{
		perror("select failed");
		exit(1);
	}

	//if timed out, print error and disconnect
	if(isReady == 0 || !FD_ISSET(fd, &fds))
	{
		return false;		
	}

	return true;

}

//writes a line to a stream
void writeLine(int *fd, void *line, int len)
{
	fd_set fds;
	//will say if the socket is ready for reading
	int isReady;


	FD_ZERO(&fds);
	FD_SET(*fd, &fds);

	//set a time out for the server
	struct timeval t;

	//set time out for 30 seconds
	t.tv_sec = 30;
	t.tv_usec = 0;

	//test to see if the socket is ready for reading
	if((isReady = select(FD_SETSIZE, NULL, &fds, NULL, &t)) < 0)
	{
		perror("select failed");
		exit(1);
	}

	//if timed out, print error and disconnect
	if(isReady == 0 || !FD_ISSET(*fd, &fds))
	{
		printf("Server timed out for writing\n");
		close(*fd);
		*fd = -1;
		return;
	}


	//will hold the bytes written to the socket
	//data size is the size of the final data
	ssize_t bytesWritten;

	//write to server
	bytesWritten = write(*fd, line, len);


	//if write failed, print error and exit
	if(bytesWritten < 0)
	{
		perror("Error writing to server");
		exit(1);
	}

}


void *chk_realloc(void *addr, size_t len)
{
	void *data = NULL;

	if((data = realloc(addr, len)) == NULL)
	{
		perror("Mem allocation failure");
		exit(1);
	}

	return data;



}

void *chk_malloc(size_t len)
{
	void *data = NULL;

	if((data = malloc(len)) == NULL)
	{
		perror("Mem allocation failure");
		exit(1);
	}

	return data;



}



/*
   This function will connect to the server
   @param hostname: hostname of the server
   @param port: port the server is listening on
   @return: file descriptor of the socket
   */
int connectToServer(char *hostname, char *port)
{
	struct addrinfo hints, *res, *res0;
	int sockfd = -1;

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = PF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	

	if (getaddrinfo(hostname, port, &hints, &res0)) {
		perror("Error resolving hostname");
		return -1;
	}
	for (res = res0; res; res = res->ai_next) {

		sockfd = socket(res->ai_family, res->ai_socktype,
				res->ai_protocol);
		if (sockfd < 0) {
			continue;
		}

		if (connect(sockfd, res->ai_addr, res->ai_addrlen) < 0) {
			close(sockfd);
			sockfd = -1;
			continue;
		}

		break;  /* okay we got one */
	}
	if (sockfd < 0) {
		perror("Failed to connect");
		return -1;
		/*NOTREACHED*/
	}
	freeaddrinfo(res0);
	return sockfd;

}
